<?php
declare(strict_types=1);

namespace app\shop\library;

use app\admin\library\module\Server;
use app\admin\model\shop\Setting;
use app\shop\enums\payment\PayWay;
use think\db\BaseQuery;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\facade\Cache;
use think\facade\Db;
use think\Model;
use Throwable;

class ShopHelper
{
    /**
     * 数组转json (不转义中文)
     * @param array $array
     * @return string
     */
    public static function array_to_json(array $array): string
    {
        if (!is_array($array)) return $array;

        return json_encode($array, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    }

    /**
     * json转数组
     * @param string|null $json
     * @return array
     */
    public static function json_to_array(string|null $json): array
    {
        if (is_null($json) || !self::is_json($json)) return [];

        return json_decode($json, true) ?? [];
    }

    /**
     * 下划线转驼峰
     * @param string $str
     * @param string $type 转化的驼峰类型 small|big
     * @return string
     */
    public static function underline_to_hump(string $str, string $type = 'small'): string
    {
        $str = array_filter(explode('_', $str));

        $str = implode(array_map('ucfirst', array_map('strtolower', $str)));

        return $type === 'big' ? ucfirst($str) : lcfirst($str);
    }

    /**
     * 驼峰转下划线
     * @param string $str
     * @return string
     */
    public static function hump_to_underline(string $str): string
    {
        return strtolower(preg_replace('/([a-z])([A-Z])/', '$1' . '_' . '$2', $str));
    }

    /**
     * 获取所有地区数据(带缓存)
     * @return mixed
     */
    public static function get_all_area(): mixed
    {
        $key = 'area_data';

        $list = Cache::get($key);
        if ($list) {
            return $list;
        }

        try {
            $list = Db::name('area')
                ->field(['id', 'pid', 'name', 'level', 'code', 'zip'])
                ->select()
                ->toArray();

            $result = Cache::remember($key, $list);
        } catch (Throwable $e) {
            ShopLogHelper::error('地区数据查询异常', [], $e);
            $result = [];
        }

        return $result;
    }

    public static function get_area_data_by_level(int $level)
    {
        return array_filter(self::get_all_area(), function ($item) use ($level) {
            return $item['level'] <= $level;
        });
    }

    /**
     * 格式化价格
     * @param float $price
     * @param int   $decimals 小数位数
     * @return string
     */
    public static function format_price(float $price, int $decimals = 2): string
    {
        return number_format($price, $decimals, '.', '');
    }

    /**
     * 判断空（排除0和'0'）
     * @param $value
     * @return bool
     */
    public static function is_empty($value): bool
    {
        return empty($value) && $value !== 0 && $value !== '0';
    }

    /**
     * 在二维数组中查找指定值
     * @param iterable $dataset   二维数组/可遍历对象
     * @param string   $searchIdx 查找的索引
     * @param mixed    $searchVal 查找的值
     * @return bool|mixed
     */
    public static function search_in_array(iterable $dataset, string $searchIdx, mixed $searchVal): mixed
    {
        foreach ($dataset as $item) {
            if ($item[$searchIdx] == $searchVal) {
                return $item;
            }
        }
        return false;
    }

    /**
     * 数组 驼峰键名转为下划线键名
     * @param array $array
     * @return array
     */
    public static function hump_to_underline_array(array $array): array
    {
        $result = [];
        foreach ($array as $key => $value) {
            if (preg_match('/([a-z])([A-Z])/', $key)) {
                $key = ShopHelper::hump_to_underline($key);
            }
            $result[$key] = $value;
        }
        return $result;
    }

    /**
     * 数组 下划线键名转为驼峰键名
     * @param array $array
     * @return array
     */
    public static function underline_to_hump_array(array $array): array
    {
        $result = [];

        foreach ($array as $key => $value) {
            $key          = ShopHelper::underline_to_hump($key);
            $result[$key] = $value;
        }

        return $result;
    }

    /**
     * 递归去除数组中的空格
     */
    public static function trim_array($array)
    {
        foreach ($array as $key => $value) {
            if (is_null($value)) continue;
            if (is_array($value)) {
                $array[$key] = self::trim_array($value);
            } else {
                if (is_string($value)) {
                    $value = trim($value);
                }
                $array[$key] = $value;
            }
        }
        return $array;
    }

    /**
     * 当前时间 Y-m-d H:i:s 格式
     * @return string
     */
    public static function now_time(): string
    {
        return date('Y-m-d H:i:s');
    }

    /**
     * 判断是否json
     * @param $string
     * @return bool
     */
    public static function is_json($string): bool
    {
        if (is_string($string)) {
            json_decode($string);
        }

        return (json_last_error() == JSON_ERROR_NONE);
    }

    /**
     * 获取sql语句
     * @param BaseQuery|Model $query 模型对象或模型数据集
     * @return string
     */
    public static function get_sql(BaseQuery|Model $query): string
    {
        //直接传入模型 获取最后一条sql语句
        if ($query instanceof Model) {
            return $query->getLastSql();
        }

        return $query->fetchSql()->select();
    }

    /**
     * 获取系统设置项
     * @param string $name    变量名，传递此参数来获取某个配置项
     * @param mixed  $default 默认值
     * @return mixed
     */
    public static function get_setting(string $name = '', mixed $default = null): mixed
    {
        $config = Setting::cache($name, null, Setting::$cacheTag)->where('name', $name)->findOrEmpty();

        if ($config->isEmpty() || !$config->value) {
            return $default;
        } else {
            $config = $config['value'];
        }

        return $config;
    }

    /**
     * 获取系统设置分组项
     * @param string $group   变量分组，传递此参数来获取某个分组的所有配置项
     * @param bool   $concise 是否开启简洁模式，简洁模式下，获取多项配置时只返回配置的键值对
     * @param bool   $groupBy 是否开启分组模式，分组模式下，获取多项配置时以分组名作为键名将配置项分组返回
     * @return mixed
     */
    public static function get_setting_group(string $group = '', bool $concise = true, bool $groupBy = false): array
    {
        try {
            $cacheKey = 'group_' . $group;
            $temp     = Setting::cache($cacheKey, null, Setting::$cacheTag)->where('group', $group)->select()->toArray();
            if (count($temp) === 1) {
                ['name' => $name, 'group' => $group, 'value' => $value, 'type' => $type] = $temp[0];
                // 获取整个分组里的所有配置项
                if ($name == $group && $type === 'array') {
                    $tempKeys = array_column($value, 'key');
                    Cache::delete($cacheKey);
                    $temp = Setting::cache($cacheKey, null, Setting::$cacheTag)->where('group', 'in', $tempKeys)->select()->toArray();
                }
            }
            $result = $temp;
            // 简洁模式
            if ($concise) {
                $result = array_column($temp, 'value', 'name');
            }
            // 分组模式
            if ($groupBy) {
                $groupedData = [];
                foreach ($temp as $item) {
                    $group = $item['group'];
                    if (!isset($groupedData[$group])) {
                        $groupedData[$group] = [];
                    }
                    $groupedData[$group][] = $item;
                }
                $result = $groupedData;
                // 分组加简洁模式
                if ($concise) {
                    $result = array_map(function ($groupItem) {
                        return array_column($groupItem, 'value', 'name');
                    }, $groupedData);
                }
            }
        } catch (DataNotFoundException|ModelNotFoundException|DbException $e) {
            ShopLogHelper::error('获取系统配置失败：', ['group' => $group], $e);
            $result = [];
        }

        return $result;
        // return $concise ? array_column($temp, 'value', 'name') : $temp;
    }

    /**
     * 根据字段合并多个二维数组
     * @param string $key       指定字段
     * @param array  ...$arrays 多个数组
     * @return array
     */
    public static function merge_arrays(string $key, array ...$arrays): array
    {
        // 创建一个空的关联数组
        $merged = [];

        foreach ($arrays as $array) {
            // 根据指定字段创建关联数组
            $assoc = array_column($array, null, $key);
            // 合并关联数组
            $merged = array_merge($assoc, $merged);
        }


        // 返回合并后的数组的值
        return array_values($merged);
    }

    /**
     * 获取完整请求域名
     * @return string 拼接在域名后的请求路径
     */
    public static function full_domain(string $urlPath = ''): string
    {
        if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
            $protocol = 'https';
        } elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
            $protocol = 'https';
        } else {
            $protocol = 'http';
        }

        $domain = $_SERVER['HTTP_HOST']; // 获取当前请求的域名

        $fullDomain = $protocol . '://' . $domain;

        if ($urlPath) {
            return $fullDomain . $urlPath;
        }

        return $fullDomain;
    }

    /**
     * 是否第三方支付
     * @param string $payWay
     * @return bool
     */
    public static function is_third_party_payment(string $payWay): bool
    {
        return in_array($payWay, [PayWay::WXPAY, PayWay::ALIPAY]);
    }

    /**
     * 科学计数法格式化字符串
     * @param int $num
     * @param int $precision
     * @return string
     */
    public static function sc_count_format_to_string(int $num, int $precision): string
    {
        if (stripos((string)$num, "e") !== false) {
            $parts = explode("e", strtolower((string)$num));
            // 使用bcmul和bcpow函数计算实际数值，并返回指定精度的字符串
            return bcmul($parts[0], bcpow('10', $parts[1], $precision), $precision);
        }
        return (string)$num;
    }

    /**
     * 获取当前使用的物流查询API服务商对应的物流公司编码字段名
     * @return string
     */
    public static function get_delivery_company_code_field(): string
    {
        if (self::get_setting('delivery_api_provider') == 'kd100') {
            return 'code_kd100';
        }

        return 'code_kdniao';
    }

    /**
     * 获取API接口URL
     * @param string $url    拼接在URL后的请求路径
     * @param bool   $suffix 是否在URL后拼接index.php
     * @return string
     */
    public static function full_api_url(string $url = '', bool $suffix = true): string
    {
        $apiUrl  = self::get_setting('api_url', self::full_domain());
        $apiName = self::get_setting('api_name', 'shop');

        if (str_ends_with($apiUrl, '/')) {
            $apiUrl = rtrim($apiUrl, '/');
        }
        if (!str_starts_with($apiName, '/')) {
            $apiName = '/' . $apiName;
        }

        // 常驻内存下不拼接index.php
        if ($suffix && !self::run_in_workerman()) {
            $api = $apiUrl . '/index.php' . $apiName;
        } else {
            $api = $apiUrl . $apiName;
        }

        if ($url && !str_starts_with($url, '/')) {
            $api .= '/';
        }

        return $api . $url;
    }

    /**
     * 获取完整H5请求URL
     * @param string $url      页面地址
     * @param bool   $hashMode 是否使用hash路由
     * @return string
     */
    public static function full_h5_url(string $url = '', bool $hashMode = true): string
    {
        $h5Domain = self::get_setting('h5_domain', '');

        if (!str_ends_with($h5Domain, '/')) {
            $h5Domain .= '/';
        }
        if ($hashMode) {
            $h5Domain .= '#';
        }
        if ($url && !str_starts_with($url, '/')) {
            $url = '/' . $url;
        }

        return $h5Domain . $url;
    }

    /**
     * 是否安装了某个模块
     * @param string $name 模块唯一标识名称
     * @return bool
     */
    public static function installed_module(string $name): bool
    {
        return !empty(Server::getIni(root_path('modules/' . $name)));
    }

    /**
     * 是否运行于workerman环境
     * @return bool
     */
    public static function run_in_workerman(): bool
    {
        return $_SERVER['SERVER_SOFTWARE'] === 'WorkerMan Development Server';
    }
}